const express = require('express');
const app = express();
const bodyParser = require('body-parser');
const Blockchain = require('./blockchain');
const { v4: uuid } = require('uuid');
const rp = require('request-promise');

const port = process.argv[2];

const NodeAddress = uuid().split('-').join('');
const bitcoin = new Blockchain();

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false}));


app.get('/', function(req, res) {
    res.send('Hello world');
});

app.get('/blockchain', function(req, res) {
    res.send(bitcoin);
});

app.post('/transaction', function(req, res) {
    console.log(req.body);
    const newTransaction = req.body;
    const index = bitcoin.addPendingTransaction(newTransaction);
    res.json({note: `Transaction will add to in bolock ${index}`})
});

app.post('/transaction/broadcast', function(req, res) {
    const amount = req.body.amount;
    const sender = req.body.sender;
    const recipient = req.body.recipient;
    const newTransaction = bitcoin.createNewTransaction(amount, sender, recipient);
    bitcoin.addPendingTransaction(newTransaction);

    const requestPromises = [];
    bitcoin.network_nodes.forEach(node_url => {
        const requestOptions = {
            uri: node_url + '/transaction',
            method: 'POST',
            body: newTransaction,
            json: true
        };
        requestPromises.push(rp(requestOptions));
    });

    Promise.all(requestPromises).then(data => {
        res.json({
                note: 'New transaction create and broadcast successfully.',
                new_transaction: newTransaction
            });
    });
});

app.get('/mine', function(req, res) {
    const lastBlock = bitcoin.getLastBlock();
    const previewBlockHash = lastBlock['hash'];
    const currentBlockData = {
        transactions: bitcoin.pending_transactions,
        index: lastBlock['index'] + 1
    }

    const nonce = bitcoin.proofOfWork(previewBlockHash, currentBlockData);
    const currentBlockHash = bitcoin.hash(previewBlockHash, currentBlockData, nonce);
    const newBlock = bitcoin.createNewBlock(nonce, previewBlockHash, currentBlockHash);

    const regNodesPromises = [];
    bitcoin.network_nodes.forEach(node_url => {
        const requestOptions = {
            uri: node_url + '/receive-new-block',
            method: 'POST',
            body: { new_block: newBlock },
            json: true
        };
        regNodesPromises.push(rp(requestOptions));
    });

    Promise.all(regNodesPromises)
        .then(data => {
            const requestOptions = {
                uri: bitcoin.node_url + '/transaction/broadcast',
                method: 'POST',
                body: {
                    amount: 12.5,
                    sender: "00",
                    recipient: NodeAddress
                },
                json: true
            }
            return rp(requestOptions);
        })
        .then(data => {
            res.json({
                note: "New block mined and broadcast successfullly",
                block: newBlock
            })
        })
        .catch(error => {
            res.status(500).json({ note: 'Error :', error: error.message });
        });
});

app.post('/receive-new-block', function(req, res) {
    const newBlock = req.body.new_block;
    const lastBlock = bitcoin.getLastBlock();
    const correctHash = (newBlock.previous_block_hash === lastBlock.hash);
    const correctIndex = (newBlock['index'] === lastBlock['index'] + 1);

    if (correctHash && correctIndex) {
        bitcoin.chain.push(newBlock);
        bitcoin.pending_transactions = [];
        res.json({
            note: 'New block received and accepted.',
            new_block: newBlock
        });
    } else {
        console.log(`${bitcoin.node_url} rejected new block. hash ${correctHash}; index ${correctIndex}`)
        res.json({
            note: 'New block rejected.',
            new_block: newBlock
        });
    }
});

app.post('/register-and-broadcast-node', function(req, res) {
    const newNodeUrl = req.body.node_url;
    if (bitcoin.network_nodes.indexOf(newNodeUrl) == -1) {
        bitcoin.network_nodes.push(newNodeUrl);
    } else {
        res.json({ note: `Node ${newNodeUrl} already exists.` });
    }

    const regNodesPromises = [];
    bitcoin.network_nodes.forEach(node_url => {
        const requestOptions = {
            uri: node_url + '/register-node',
            method: 'POST',
            body: { node_url: newNodeUrl },
            json: true
        };
        console.log(`register-and-broadcast-node ${node_url} register-node`);
        regNodesPromises.push(rp(requestOptions));
    });

    Promise.all(regNodesPromises)
        .then(data => {
            const bulkRegisterOptions = {
                uri: newNodeUrl + '/register-nodes-bulk',
                method: 'POST',
                body: { all_network_nodes: [ ...bitcoin.network_nodes, bitcoin.node_url ] },
                json: true
            };
            return rp(bulkRegisterOptions);
        })
        .then(data => {
            res.json({ note: 'New node registered with network successfully.' });
        })
        .catch(error => {
            res.status(500).json({ note: 'Error registering the node.', error: error.message });
        });
});

app.post('/register-node', function(req, res) {
    const newNodeUrl = req.body.node_url;
    const isNodeAlreadyPresent = (bitcoin.network_nodes.indexOf(newNodeUrl) != -1);
    const isCurrentNode = (bitcoin.node_url == newNodeUrl);
    if (!isNodeAlreadyPresent && !isCurrentNode) {
        bitcoin.network_nodes.push(newNodeUrl);
        res.json({ note: 'New node registered successfully with node.'});
    } else {
        res.json({ note: `${newNodeUrl} node exist`});
    }
});

app.post('/register-nodes-bulk', function(req, res) {
    const allNodes = req.body.all_network_nodes;
    allNodes.forEach(network_node => {
        const isNotAlreadyPresent = (bitcoin.network_nodes.indexOf(network_node) == -1);
        const isNotCurrentNode = (bitcoin.node_url != network_node);
        if (isNotAlreadyPresent && isNotCurrentNode) {
            bitcoin.network_nodes.push(network_node);
        }
    })

    res.json( { note: 'Bulk registration successfully.'});
});

app.get('/consensus', function(req, res) {
    const regNodesPromises = []; 
    bitcoin.network_nodes.forEach(node_url => {
        const requestOptions = {
            uri: node_url + '/blockchain',
            method: 'GET',
            json: true
        };
        regNodesPromises.push(rp(requestOptions))
    })

    Promise.all(regNodesPromises)
        .then(blockchains => {
            const chainLength = bitcoin.chain.length;

            let maxChainLength = chainLength;
            let newLongestChain = null;
            let newPendingTransactions = null;

            blockchains.forEach(blockchain => {
                if (blockchain.chain.length > maxChainLength) {
                    maxChainLength = blockchain.chain.length;
                    newLongestChain = blockchain.chain;
                    newPendingTransactions = blockchain.pending_transactions;
                }
            });

            if (!newLongestChain || (newLongestChain && !bitcoin.isChainValid(newLongestChain))) {
                res.json({
                    note: 'Current chain has not been replaced.',
                    chain: bitcoin.chain
                });
            } else if (newLongestChain && bitcoin.isChainValid(newLongestChain)) {
                bitcoin.chain = newLongestChain;
                bitcoin.pending_transactions = newPendingTransactions;
                res.json({
                    note: 'Current chain has been replaced.',
                    chain: bitcoin.chain
                });
            } else {
                // do nothing
            }

        });
});

app.get('/block/:blockHash', function(req, res) {
    const blockHash = req.params.blockHash;
    const block = bitcoin.getBlock(blockHash);
    res.json({
        block: block
    });
});

app.get('/transaction/:transactionId', function(req, res) {
    const transactionId = req.params.transactionId;
    const transactionData = bitcoin.getTransaction(transactionId);
    res.json({
        transaction: transactionData.transaction,
        block: transactionData.block
    });
});

app.get('/address/:address', function(req, res) {
    const address = req.params.address;
    const addressData = bitcoin.getAddressData(address);
    res.json({
        address_data: addressData
    });
});

app.get('/blockchain-explor', function(req, res) {
    res.sendFile("./index.html", {root: __dirname });
});

app.listen(port, function() {
    console.log(`Listening on port ${port} ...`)
});